/*! ******************************************************************************
 *
 * Pentaho Data Integration
 *
 * Copyright (C) 2002-2017 by Hitachi Vantara : http://www.pentaho.com
 *
 *******************************************************************************
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 ******************************************************************************/

package org.pentaho.di.trans.steps.xmlinputsax;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.pentaho.di.core.Const;
import org.pentaho.di.core.exception.KettleValueException;
import org.pentaho.di.core.logging.LogChannelInterface;
import org.pentaho.di.core.xml.XMLParserFactoryProducer;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;
import org.xml.sax.helpers.DefaultHandler;

/**
 * Parse XML document using SAX and retreive fields
 *
 * @author Youssef
 * @since 22-may-2006
 */
public class XMLInputSaxFieldRetriever extends DefaultHandler {

  List<XMLInputSaxField> fields;

  int[] position = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 };

  // list of elements to the root element
  private List<XMLInputSaxFieldPosition> pathToRootElement = new ArrayList<XMLInputSaxFieldPosition>();

  // list of elements to the root element
  private List<XMLInputSaxFieldPosition> _pathToRootElement = new ArrayList<XMLInputSaxFieldPosition>();

  // count the deep to the current element in pathToStartElement
  private int counter = 0;

  // count the deep to the current element in xml file
  private int _counter = -1;

  // true when the root element is reached
  private boolean rootFound = false;

  // source xml file name
  private String sourceFile;

  private XMLInputSaxMeta meta;

  private LogChannelInterface log;

  // private String tempVal;

  public XMLInputSaxFieldRetriever( LogChannelInterface log, String sourceFile, XMLInputSaxMeta meta ) {
    this.log = log;
    for ( int i = 0; i < meta.getInputPosition().length; i++ ) {
      this.pathToRootElement.add( meta.getInputPosition()[i] );
    }
    this.meta = meta;
    this.sourceFile = sourceFile;
    fields = new ArrayList<XMLInputSaxField>();
  }

  public List<XMLInputSaxField> getFields() {
    parseDocument();
    return this.fields;
  }

  private void parseDocument() {
    // get a factory
    SAXParserFactory spf = null;
    try {
      spf = XMLParserFactoryProducer.createSecureSAXParserFactory();
    } catch ( SAXNotSupportedException | SAXNotRecognizedException | ParserConfigurationException ex ) {
      log.logError( ex.getMessage() );
    }
    try {
      // get a new instance of parser
      SAXParser sp = spf.newSAXParser();
      // parse the file and also register this class for call backs
      sp.parse( sourceFile, this );

    } catch ( SAXException se ) {
      log.logError( Const.getStackTracker( se ) );
    } catch ( ParserConfigurationException pce ) {
      log.logError( Const.getStackTracker( pce ) );
    } catch ( IOException ie ) {
      log.logError( Const.getStackTracker( ie ) );
    }
  }

  private void counterUp() {
    if ( counter == pathToRootElement.size() - 1 ) {
      rootFound = true;
      counter++;
    } else {
      counter++;
    }
  }

  private boolean comparePaths( int count ) {
    for ( int i = 0; i <= count; i++ ) {
      if ( !_pathToRootElement.get( i ).equals( pathToRootElement.get( i ) ) ) {
        return false;
      }
    }
    return true;
  }

  private void counterDown() {
    if ( ( counter - 1 == _counter ) && comparePaths( _counter ) ) {
      _pathToRootElement.remove( _counter );
      counter--;
      _counter--;
      rootFound = false;
    } else {
      _pathToRootElement.remove( _counter );
      _counter--;
    }
  }

  private String naming( XMLInputSaxFieldPosition[] path ) {
    String ret = "";
    for ( int i = pathToRootElement.size(); i < path.length; i++ ) {
      String name;
      if ( path[i].getType() == XMLInputSaxFieldPosition.XML_ELEMENT_ATT ) {
        name = path[i].getAttributeValue();
      } else {
        name = path[i].getName() + path[i].getElementNr();
      }
      if ( i > pathToRootElement.size() ) {
        ret += "_" + name;
      } else {
        ret += name;
      }
    }
    return ret;
  }

  // Event Handlers
  public void startElement( String uri, String localName, String qName, Attributes attributes ) throws SAXException {
    // set the _counter level
    position[_counter + 1] += 1;
    _counter++;
    try {
      if ( !rootFound ) {
        XMLInputSaxFieldPosition el = null;

        try {
          el = pathToRootElement.get( counter );
        } catch ( IndexOutOfBoundsException e ) {
          throw new SAXException( e );
        }
        if ( ( counter == _counter ) && qName.equalsIgnoreCase( el.getName() ) ) {
          if ( el.getType() == XMLInputSaxFieldPosition.XML_ELEMENT_ATT ) {
            String att1 = attributes.getValue( el.getAttribute() ); // must throw exception
            String att2 = el.getAttributeValue();
            if ( att1.equals( att2 ) ) {
              _pathToRootElement.add( new XMLInputSaxFieldPosition( qName, el.getAttribute(), el
                .getAttributeValue() ) ); // to
                                          // test
                                          // with
                                          // clone
              if ( counter == pathToRootElement.size() - 1 ) {
                for ( int i = 0; i < attributes.getLength(); i++ ) {
                  XMLInputSaxFieldPosition tempP =
                    new XMLInputSaxFieldPosition(
                      attributes.getQName( i ), XMLInputSaxFieldPosition.XML_ATTRIBUTE, i + 1 );
                  _pathToRootElement.add( tempP );
                  XMLInputSaxFieldPosition[] path = new XMLInputSaxFieldPosition[_pathToRootElement.size()];
                  _pathToRootElement.toArray( path );
                  _pathToRootElement.remove( _pathToRootElement.size() - 1 );
                  XMLInputSaxField tempF = new XMLInputSaxField( tempP.getName(), path );
                  if ( !fields.contains( tempF ) ) {
                    fields.add( tempF );
                  }
                }
              }
              counterUp();
            } else {
              _pathToRootElement.add( new XMLInputSaxFieldPosition(
                qName, XMLInputSaxFieldPosition.XML_ELEMENT_POS, position[_counter] + 1 ) );
            }
          } else {
            _pathToRootElement.add( new XMLInputSaxFieldPosition(
              qName, XMLInputSaxFieldPosition.XML_ELEMENT_POS, position[_counter] + 1 ) );
            counterUp();
          }
        } else {
          _pathToRootElement.add( new XMLInputSaxFieldPosition(
            qName, XMLInputSaxFieldPosition.XML_ELEMENT_POS, position[_counter] + 1 ) );
        }
      } else {
        XMLInputSaxField temp = null;
        if ( attributes.getValue( meta.getDefiningAttribute( qName ) ) == null ) {
          _pathToRootElement.add( new XMLInputSaxFieldPosition(
            qName, XMLInputSaxFieldPosition.XML_ELEMENT_POS, position[_counter] + 1 ) );
          XMLInputSaxFieldPosition[] path = new XMLInputSaxFieldPosition[_pathToRootElement.size()];
          _pathToRootElement.toArray( path );
          temp = new XMLInputSaxField( naming( path ), path );
        } else {
          String attribute = meta.getDefiningAttribute( qName );
          _pathToRootElement
            .add( new XMLInputSaxFieldPosition( qName, attribute, attributes.getValue( attribute ) ) );
          XMLInputSaxFieldPosition[] path = new XMLInputSaxFieldPosition[_pathToRootElement.size()];
          _pathToRootElement.toArray( path );
          temp = new XMLInputSaxField( naming( path ), path );
        }

        if ( !fields.contains( temp ) ) {
          fields.add( temp );
        }
      }
    } catch ( KettleValueException e ) {
      log.logError( Const.getStackTracker( e ) );
      throw new SAXException( _counter
        + "," + counter + _pathToRootElement.get( _pathToRootElement.size() - 1 ).toString(), e );

    }
  }

  public void characters( char[] ch, int start, int length ) throws SAXException {
    // tempVal = new String(ch,start,length);
  }

  public void endElement( String uri, String localName, String qName ) throws SAXException {
    position[_counter + 1] = -1;
    counterDown();
  }

  /*
   * public static void main(String[] args){ XMLvInputFieldPosition[] path=new XMLvInputFieldPosition[3]; try {
   * path[0]=new XMLvInputFieldPosition("Ep=raml"); path[1]=new XMLvInputFieldPosition("Ep=cmData"); path[2]=new
   * XMLvInputFieldPosition("Ea=managedObject/class:BTS"); } catch (KettleValueException e) { // TODO Auto-generated
   * catch block LogWriter.getInstance().logError(toString(), Const.getStackTracker(e)); } //System.out.println(new
   * xmlElement("hello","hello","hello").equals(new xmlElement("hello","hello","hello"))); XMLvSaxFieldRetreiver spe =
   * new XMLvSaxFieldRetreiver("D:\\NOKIA\\Project\\Ressources\\CASA-1.XML",path,"name"); ArrayList l=spe.getFields();
   * System.out.println(l.size()); for(int i=0;i<l.size();i++){
   * System.out.println(((XMLvInputField)l.get(i)).getFieldPositionsCode(3)); } }
   */
}
